package sf.jooq;

import org.jooq.Attachable;
import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.Delete;
import org.jooq.Field;
import org.jooq.Insert;
import org.jooq.Record;
import org.jooq.SQLDialect;
import org.jooq.Select;
import org.jooq.SelectConditionStep;
import org.jooq.SelectQuery;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.UniqueKey;
import org.jooq.Update;
import org.jooq.impl.DSL;
import org.jooq.impl.JooqVisitor;
import sf.common.wrapper.Page;
import sf.core.DBObject;
import sf.database.dao.DBContext;
import sf.database.dialect.DBDialect;
import sf.database.jdbc.sql.Crud;
import sf.database.jdbc.sql.OrmIterator;
import sf.database.jdbc.sql.OrmStream;
import sf.database.util.DBUtils;
import sf.jooq.tables.JooqTable;
import sf.spring.util.CollectionUtils;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class JooqImpl implements JooqInf {
    @Override
    public <T extends DBObject> boolean jooqExists(Connection conn, SQLDialect sqlDialect, List<Condition> condition, Class<T> clz) {
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        DBContext context = DBUtils.doGetDBContext(conn);
        JooqTable table = JooqTables.getTable(context, clz, dialect);
        if (sqlDialect == null) {
            sqlDialect = dialect.getJooqDialect();
        }
        if (sqlDialect == null) {
            sqlDialect = SQLDialect.DEFAULT;
        }
        Select select = DSL.using(sqlDialect).selectCount().from(table).where(condition).limit(1);
        String sql = select.getSQL();
        List<Object> values = select.getBindValues();
        return Crud.getInstance().getCrudSql().selectOne(conn, Boolean.class, sql, values.toArray());
    }

    @Override
    public <T> T jooqSelectOne(Connection conn, Select select, Class<T> returnClass) {
        setDialect(conn, select);
        String sql = select.getSQL();
        List<Object> values = select.getBindValues();
        List<Field<?>> fields = select.getSelect();
        Class<?> beanClass = returnClass;
        if (beanClass == null) {
            beanClass = selectQueryReturnClass((SelectConditionStep) select, fields);
        }
        return Crud.getInstance().getCrudSql().selectOne(conn, (Class<T>) beanClass, sql, values.toArray());
    }

    @Override
    public <T> List<T> jooqSelectList(Connection conn, Select select, Class<T> returnClass) {
        setDialect(conn, select);
        String sql = select.getSQL();
        List<Object> values = select.getBindValues();
        List<Field<?>> fields = select.getSelect();
        Class<?> beanClass = returnClass;
        if (beanClass == null) {
            beanClass = selectQueryReturnClass((SelectConditionStep) select, fields);
        }
        return Crud.getInstance().getCrudSql().selectList(conn, (Class<T>) beanClass, sql, values.toArray());
    }

    @Override
    public <T> Page<T> jooqSelectPage(Connection conn, Select<?> countSelect, Select<?> pageSelect, Class<T> returnClass) {
        setDialect(conn, countSelect);
        setDialect(conn, pageSelect);
        String sql = pageSelect.getSQL();
//        ((SelectLimitStep<?>)listSelect).offset(start).limit(limit);
        Integer start = JooqVisitor.getOffset(pageSelect);
        Integer limit = JooqVisitor.getLimit(pageSelect);
        start = start == null || start < 0 ? 0 : start;
        limit = limit == null || limit < 0 ? Integer.MAX_VALUE : limit;
        List<Object> values = pageSelect.getBindValues();
        List<Field<?>> fields = pageSelect.getSelect();
//        String coungSql = JooqVisitor.getCountSql(select.configuration(), select);
        Class<?> beanClass = returnClass;
        if (beanClass == null) {
            beanClass = selectQueryReturnClass((SelectConditionStep) pageSelect, fields);
        }
        return Crud.getInstance().getCrudSql().selectRealPageRaw(conn, start, limit, (Class<T>) beanClass, countSelect.getSQL(), countSelect.getBindValues().toArray(), sql, values.toArray());
    }

    @Override
    public <T> void selectIterator(Connection conn, OrmIterator<T> ormIt, Select select, Class<T> returnClass) {
        setDialect(conn, select);
        String sql = select.getSQL();
        List<Object> values = select.getBindValues();
        List<Field<?>> fields = select.getSelect();
        Class<?> beanClass = returnClass;
        if (beanClass == null) {
            beanClass = selectQueryReturnClass((SelectConditionStep) select, fields);
        }
        Crud.getInstance().getCrudSql().selectIterator(conn, ormIt, (Class<T>) beanClass, false, sql, values.toArray());
    }

    @Override
    public <T> void selectStream(Connection conn, OrmStream<T> ormStream, Select select, Class<T> returnClass) {
        setDialect(conn, select);
        String sql = select.getSQL();
        List<Object> values = select.getBindValues();
        List<Field<?>> fields = select.getSelect();
        Class<?> beanClass = returnClass;
        if (beanClass == null) {
            beanClass = selectQueryReturnClass((SelectConditionStep) select, fields);
        }
        Crud.getInstance().getCrudSql().selectStream(conn, ormStream, (Class<T>) beanClass, false, sql, values.toArray());
    }

    private void setDialect(Connection conn, Attachable select) {
        Configuration configuration = select.configuration();
        if (configuration.dialect() == SQLDialect.DEFAULT || configuration.dialect() == null) {
            SQLDialect sqlDialect = DBUtils.doGetDialect(conn, false).getJooqDialect();
            if (sqlDialect != null) {
                //需要从连接中动态获取dialect
                select.configuration().set(sqlDialect);
            } else {
                select.configuration().set(SQLDialect.DEFAULT);
            }
        }
    }

    /**
     * @param select
     * @param fields
     * @return
     */
    private Class<?> selectQueryReturnClass(SelectConditionStep select, List<Field<?>> fields) {
        Class<?> beanClass = Map.class;
        SelectQuery query = select.getQuery();
        List<? extends Table<?>> fromTables = JooqVisitor.getSelectFromTableList(query);
        if (!fields.isEmpty()) {
            //是否相同
            boolean same = true;
            TableField f = (TableField) fields.get(0);
            Table table = f.getTable();
            for (Field tf : fields) {
                TableField temp = (TableField) tf;
                if (!Objects.equals(f.getTable(), temp.getTable())) {
                    same = false;
                    break;
                }
            }
            if (same) {
                if (fields.size() == 1) {
                    //单一值返回
                    beanClass = Object.class;
                } else {
                    if (JooqTable.class.isAssignableFrom(table.getClass())) {
                        beanClass = ((JooqTable) table).getClz();
                    }
                }
            }
        } else if (fromTables.size() == 1) {
            Table table = fromTables.get(0);
            if (JooqTable.class.isAssignableFrom(table.getClass())) {
                beanClass = ((JooqTable) table).getClz();
            }
        }
        return beanClass;
    }

    @Override
    public <T extends Record> int jooqInsert(Connection conn, Insert<T> insert) {
        setDialect(conn, insert);
        String sql = insert.getSQL();
        List<Object> values = insert.getBindValues();
        return Crud.getInstance().getCrudSql().execute(conn, sql, values.toArray());
    }

    @Override
    public <T extends Record> int jooqInsert(Connection conn, Insert<T> insert, Table<T> table, Map<String, Object> keyValues) {
        setDialect(conn, insert);
        String sql = insert.getSQL();
        List<Object> values = insert.getBindValues();
        List<String> pkeys = new ArrayList<>();

        if (table != null && table.getPrimaryKey() != null) {
            UniqueKey<T> uk = table.getPrimaryKey();
            if (uk != null) {
                List<TableField<T, ?>> list = uk.getFields();
                if (CollectionUtils.isNotEmpty(list)) {
                    for (TableField<T, ?> f : list) {
                        pkeys.add(f.getName());
                    }
                }
            }
        }
        return Crud.getInstance().getCrudSql().execute(conn, sql, values.toArray(), false, pkeys, keyValues);
    }

    @Override
    public <T extends Record> int jooqUpdate(Connection conn, Update<T> update) {
        setDialect(conn, update);
        String sql = update.getSQL();
        List<Object> values = update.getBindValues();
        return Crud.getInstance().getCrudSql().execute(conn, sql, values.toArray());
    }

    @Override
    public <T extends Record> int jooqDelect(Connection conn, Delete<T> delete) {
        setDialect(conn, delete);
        String sql = delete.getSQL();
        List<Object> values = delete.getBindValues();
        return Crud.getInstance().getCrudSql().execute(conn, sql, values.toArray());
    }
}
